﻿using System;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Utils;
using MugenMvvmToolkit.ViewModels;
using MugenMvvmToolkitTest.Infrastructure;
using MugenMvvmToolkitTest.TestInfrastructure;
using MugenMvvmToolkitTest.TestModels;
using Should;

namespace MugenMvvmToolkitTest.ViewModel
{
#if WPF
    [Serializable]
#endif
    [DataContract(IsReference = true)]
    [TestClass]
    public class NotifyPropertyChangedBaseTest : NotifyPropertyChangedBase
    {
        #region Fields

#if WPF
        [NonSerialized]
#endif
        [XmlIgnore]
        private readonly TestThreadManager _testThreadManager;
        private ExecutionType _executionType;
        private bool _useGlobalSetting;
        private string _setProperty;
        private string _property;
        private string _propertyExp;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="NotifyPropertyChangedBaseTest" /> class.
        /// </summary>
        public NotifyPropertyChangedBaseTest()
        {
            _testThreadManager = new TestThreadManager();
            ThreadManager = _testThreadManager;
        }

        #endregion

        #region Properties

        public string SetPropertyName
        {
            get { return _setProperty; }
            set
            {
                if (_useGlobalSetting)
                {
#if NET45
                    SetProperty(ref _setProperty, value);
#else
                    SetProperty(ref _setProperty, value, "SetPropertyName");
#endif
                    return;
                }
                SetProperty(ref _setProperty, value, "SetPropertyName", _executionType);
            }
        }

        public string SetPropertyExpression
        {
            get { return _setProperty; }
            set
            {
                if (_useGlobalSetting)
                    SetProperty(ref _setProperty, value, () => SetPropertyExpression);
                else
                    SetProperty(ref _setProperty, value, () => SetPropertyExpression, _executionType);
            }
        }

        public string PropertyString
        {
            get { return _property; }
            set
            {
                if (value == _property) return;
                _property = value;
                if (_useGlobalSetting)
                {
#if NET45
                    OnPropertyChanged();
#else
                    OnPropertyChanged("PropertyString");
#endif
                }
                OnPropertyChanged("PropertyString", _executionType);
            }
        }

        public string PropertyExp
        {
            get { return _propertyExp; }
            set
            {
                if (value == _propertyExp) return;
                _propertyExp = value;
                if (_useGlobalSetting)
                    OnPropertyChanged(() => PropertyExp);
                else
                    OnPropertyChanged(() => PropertyExp, _executionType);
            }
        }

        #endregion

        #region Test infrastructure

        [TestInitialize]
        public void InitializeTest()
        {
            PropertyChangeExecutionMode = ExecutionType.None;
        }

        [TestMethod]
        public void SetPropertyNameTest()
        {
            BasePropertyTest(() => SetPropertyName, s => SetPropertyName = s, "SetPropertyName");
        }

        [TestMethod]
        public void SetPropertyExpressionTest()
        {
            BasePropertyTest(() => SetPropertyExpression, s => SetPropertyExpression = s, "SetPropertyExpression");
        }

        [TestMethod]
        public void PropertyStringTest()
        {
            BasePropertyTest(() => PropertyString, s => PropertyString = s, "PropertyString");
        }

        [TestMethod]
        public void PropertyExpTest()
        {
            BasePropertyTest(() => PropertyExp, s => PropertyExp = s, "PropertyExp");
        }

        [TestMethod]
        public void TestClearEvents()
        {
            _executionType = ExecutionType.None;
            int count = 0;
            string lastProp = null;
            PropertyChanged += (sender, args) =>
                                   {
                                       lastProp = args.PropertyName;
                                       count++;
                                   };
            OnPropertyChanged(() => SetPropertyName);
            lastProp.ShouldEqual("SetPropertyName");
            count.ShouldEqual(1);

            //Clear events
            ClearPropertyChangedEvents();
            count = 0;
            lastProp = null;
            OnPropertyChanged(() => SetPropertyName);
            lastProp.ShouldBeNull();
            count.ShouldEqual(0);
        }

        [TestMethod]
        public void TestThreadManager()
        {
            ThreadManager = null;
            GlobalSettings.DefaultThreadManager = new TestThreadManager();
            ThreadManager.ShouldEqual(GlobalSettings.DefaultThreadManager);
            var localManager = new TestThreadManager();
            ThreadManager = localManager;
            ThreadManager.ShouldEqual(localManager);
        }

        [JetBrains.Annotations.NotifyPropertyChangedInvocator("propertyName")]
        protected void BasePropertyTest(Func<string> getProperty, Action<string> updateProperty, string propertyName)
        {
            _useGlobalSetting = false;
            updateProperty(null);
            BasePropertyTestInternal(getProperty, updateProperty, propertyName, b => _executionType = b, false);
            ClearPropertyChangedEvents();
            updateProperty(null);
            _useGlobalSetting = true;

            using (SuspendNotifications())
            {
                BasePropertyTestInternal(getProperty, updateProperty, propertyName,
                                     b => PropertyChangeExecutionMode = b, true);
            }
        }

        private void BasePropertyTestInternal(Func<string> getProperty, Action<string> updateProperty, string propertyName, Action<ExecutionType> changeRaiseInUi, bool suspend)
        {
            changeRaiseInUi(ExecutionType.None);
            int count = 0;
            string lastProp = null;
            const string value = "value";
            PropertyChanged += (sender, args) =>
                                   {
                                       lastProp = args.PropertyName;
                                       count++;
                                   };

            lastProp.ShouldBeNull();
            getProperty().ShouldBeNull();
            updateProperty(value);
            getProperty().ShouldEqual(value);
            if (!suspend)
            {
                lastProp.ShouldEqual(propertyName);
                count.ShouldEqual(1);
            }
            else
                count.ShouldEqual(0);

            //Invoke in ui thread 
            updateProperty(null);
            changeRaiseInUi(ExecutionType.AsynchronousInUi);
            count = 0;
            lastProp = null;
            _testThreadManager.InvokeInUiAction = null;
            updateProperty(value);
            getProperty().ShouldEqual(value);
            lastProp.ShouldBeNull();
            if (!suspend)
            {
                _testThreadManager.InvokeInUiAction.ShouldNotBeNull();
                _testThreadManager.InvokeInUiAction.Invoke();
                lastProp.ShouldEqual(propertyName);
                count.ShouldEqual(1);
            }
            else
            {
                _testThreadManager.InvokeInUiAction.ShouldBeNull();
                count.ShouldEqual(0);
            }

            //Invoke in ui thread synchronous
            updateProperty(null);
            changeRaiseInUi(ExecutionType.SynchronousInUi);
            count = 0;
            lastProp = null;
            _testThreadManager.InvokeInUiSynchronousAction = null;
            updateProperty(value);
            getProperty().ShouldEqual(value);
            lastProp.ShouldBeNull();
            if (!suspend)
            {
                _testThreadManager.InvokeInUiSynchronousAction.ShouldNotBeNull();
                _testThreadManager.InvokeInUiSynchronousAction.Invoke();
                lastProp.ShouldEqual(propertyName);
                count.ShouldEqual(1);
            }
            else
            {
                _testThreadManager.InvokeInUiSynchronousAction.ShouldBeNull();
                count.ShouldEqual(0);
            }
        }

        #endregion
    }

    [TestClass]
    public class NotifyPropertyChangedBaseSerializationTest : SerializationTestBase<NotifyPropertyChangedBaseTest>
    {
        #region Overrides of SerializationTestBase

        public override NotifyPropertyChangedBaseTest GetObject()
        {
            return new NotifyPropertyChangedBaseTest() { PropertyChangeExecutionMode = ExecutionType.None };
        }

        public override void AssertObject(NotifyPropertyChangedBaseTest deserializedObj)
        {
            deserializedObj.PropertyChangeExecutionMode.ShouldEqual(GlobalSettings.PropertyChangeExecutionMode);
        }

        #endregion
    }
}